﻿using System.Net;
using System.Text;
using System.Xml;
using up6.db.model;
using MimeTypes;
using System.Net.Http.Headers;

namespace up6.store.minio;

public class MinioTool
{
    /// <summary>
    /// API:
    /// https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_PutObject.html
    /// </summary>
    /// <param name="key">/123.txt</param>
    /// <param name="data"></param>
    /// <returns></returns>
    public static async Task<string> putObjectAsync(string key, byte[] data)
    {
        MinioConfig cfg = new MinioConfig();
        //WebClient c = new WebClient();
        //OkHttpClient c = new OkHttpClient();
        //http://192.168.0.111:9001/test//123.txt
        string url = cfg.endpoint + "/" + cfg.bucket + "/" + key;
        //MediaType JSON = MediaType.parse("application/octet-stream; charset=utf-8");

        MinioAuthorization auth = new MinioAuthorization();
        auth.setConfig(cfg)
        .setMethod("PUT")
        .setData(data)
        .setUrl(url)
        //.addHead("content-length",data.Length.ToString())
        //.addHead("content-type", "application/octet-stream; charset=utf-8")
        .delHead("content-md5")
        .setContentType("application/octet-stream");

        try
        {
            var authstr = auth.Authorization();
            var c = new HttpClient();
            c.DefaultRequestHeaders.Add("Content-Type", "application/octet-stream");
            c.DefaultRequestHeaders.Add("Content-Length", data.Length.ToString());
            c.DefaultRequestHeaders.Add("x-amz-date", auth.getTimeIso());
            c.DefaultRequestHeaders.Add("x-amz-content-sha256", auth.data_sha256_Hash);
            c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", authstr);
            var content = new MultipartFormDataContent();
            content.Add(new ByteArrayContent(data, 0, data.Length));
            var res = await c.PutAsync(url, content);
            if (res.IsSuccessStatusCode &&
                res.Headers.Contains("ETag"))
            {
                var hs = res.Headers.GetValues("ETag").ToArray();
                if(hs.Count()>0)
                {
                    return hs[0].Replace("\"", "");
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }
        return string.Empty;
    }
    public static async Task<string> putObjectAsync(string key, string pathLoc)
    {
        MinioConfig cfg = new MinioConfig();
        //http://192.168.0.111:9001/test//123.txt
        string url = cfg.endpoint + "/" + cfg.bucket + "/" + key;

        var tp = MimeTypeMap.GetExtension(pathLoc,false);
        if (string.IsNullOrEmpty(tp)) tp = "application/octet-stream";

        var data = File.ReadAllBytes(pathLoc);

        MinioAuthorization auth = new MinioAuthorization();
        auth.setConfig(cfg)
        .setMethod("PUT")
        .setData(data)
        .setUrl(url)
        //.addHead("content-length",data.Length.ToString())
        //.addHead("content-type", "application/octet-stream; charset=utf-8")
        .delHead("content-md5")
        .setContentType(tp);

        try
        {
            var authstr = auth.Authorization();
            var c = new HttpClient();
            c.DefaultRequestHeaders.Add("Content-Type", tp);
            //c.Headers.Add("Content-Length", data.Length.ToString());
            c.DefaultRequestHeaders.Add("x-amz-date", auth.getTimeIso());
            c.DefaultRequestHeaders.Add("x-amz-content-sha256", auth.data_sha256_Hash);
            c.DefaultRequestHeaders.Add("Authorization", authstr);
            var content = new MultipartFormDataContent();
            content.Add(new ByteArrayContent(data, 0, data.Length));
            var res = await c.PutAsync(url, content);
            if (res.IsSuccessStatusCode &&
                res.Headers.Contains("ETag"))
            {
                var vs = res.Headers.GetValues("ETag").ToArray();
                if (vs.Count() > 0)
                {
                    return vs[0].Replace("\"", "");
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }
        return string.Empty;
    }


    /**
     * API：
     * 	https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_GetObject.html
     * @param key 以左斜杠开始的资源路径。/guid/QQ.exe径
     * @param blockOffset
     * @param blockLen
     * @return
     */
    public static async Task<byte[]> getObjectAsync(string key, long blockOffset, long blockLen)
    {
        MinioConfig cfg = new MinioConfig();
        //http://192.168.0.111:9001/test//123.txt?uploads

        string url = cfg.endpoint + "/" + cfg.bucket + "/" + key;
        string range = "bytes=" + blockOffset.ToString() + "-" + (blockOffset + blockLen - 1).ToString();

        MinioAuthorization auth = new MinioAuthorization();

        auth.setConfig(cfg)
        .setMethod("GET")
        .setData(Encoding.UTF8.GetBytes(""))
        .setUrl(url)
        .delHead("content-length")
        .delHead("content-type")
        .delHead("Content-Md5");

        var authstr = auth.Authorization();
        var c = new HttpClient();
        c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", authstr);
        c.DefaultRequestHeaders.Range = new RangeHeaderValue(blockOffset, blockOffset + blockLen);
        c.DefaultRequestHeaders.Add("x-amz-date", auth.getTimeIso());
        c.DefaultRequestHeaders.Add("x-amz-content-sha256", auth.getDataHash());
        c.DefaultRequestHeaders.Add("Content-Type", "application/octet-stream; charset=utf-8");
        var res = await c.GetAsync(url);
        var strm = await res.Content.ReadAsStreamAsync();
        
        /**返回值
        * 	
        HTTP/1.1 206 Partial Content
        x-amz-id-2: MzRISOwyjmnupCzjI1WC06l5TTAzm7/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
        x-amz-request-id: 47622117804B3E11
        Date: Fri, 28 Jan 2011 21:32:09 GMT
        x-amz-meta-title: the title
        Last-Modified: Fri, 28 Jan 2011 20:10:32 GMT
        ETag: "b2419b1e3fd45d596ee22bdf62aaaa2f"
        Accept-Ranges: bytes
        Content-Range: bytes 0-9/443
        Content-Type: text/plain
        Content-Length: 10
        Server: AmazonS3
    
        [10 bytes of object data]
        */
        byte[] bt = new byte[(int)res.Content.Headers.ContentLength];
        var readLen = await strm.ReadAsync(bt,0,bt.Length);
        return bt;
    }

    /**
     * API：
     * 	https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_CreateMultipartUpload.html
     * @param key guid/QQ.exe，
     * @return
     */
    public static async Task<string> CreateMultipartUploadAsync(string key)
    {
        MinioConfig cfg = new MinioConfig();
        //http://192.168.0.111:9001/test//123.txt?uploads
        string url = cfg.endpoint + "/" + cfg.bucket + "/" + key + "?uploads";

        string mimeType = MimeTypeMap.GetMimeType(key);
        mimeType = mimeType ?? "application/octet-stream";

        MinioAuthorization auth = new MinioAuthorization();
        auth.setConfig(cfg)
        .setMethod("POST")
        .setData(Encoding.UTF8.GetBytes(""))
        .setUrl(url)
        .delHead("Content-Md5")
        .setContentType(mimeType);

        var authstr = auth.Authorization();
        var c = new HttpClient();
        var req = new HttpRequestMessage(HttpMethod.Post, url);
        req.Headers.Clear();
        req.Headers.Add("x-amz-date", auth.getTimeIso());
        req.Headers.Add("x-amz-content-sha256", auth.getDataHash());
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authstr);
        req.Content = new StringContent(string.Empty,new MediaTypeHeaderValue(mimeType));

        var res = await c.SendAsync(req);
        if (!res.IsSuccessStatusCode)
        {
            var msg = await res.Content.ReadAsStringAsync();
            Console.WriteLine(msg);
            throw new IOException(msg);
        }
        var body = await res.Content.ReadAsStringAsync();

        /**返回值
            <InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                <Bucket>example-bucket</Bucket>
                <Key>example-object</Key>
                <UploadId>VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA</UploadId>
            </InitiateMultipartUploadResult>
            */

        //System.out.println("返回值："+str);
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(body);

        XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable);
        nsMgr.AddNamespace("ns", "http://s3.amazonaws.com/doc/2006-03-01/");

        var uploadId = doc.DocumentElement.SelectSingleNode("//ns:UploadId", nsMgr);
        return uploadId.InnerText;
    }

    /**
     * API:
     * 	https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_UploadPart.html
     * @param key /guid/QQ.exe
     * @param part 基于1
     * @param uploadID
     * @param data
     */
    public static async Task<string> UploadPartAsync(String key, int part, String uploadID, byte[] data)
    {
        //System.out.println("\n 上传片："+key +  " partNumber="+String.valueOf(part)+" UploadId="+uploadID);

        MinioConfig cfg = new MinioConfig();
        //http://192.168.0.111:9001/test//123.txt
        String url = cfg.endpoint + "/" + cfg.bucket + "/" + key + "?partNumber=" + part.ToString() + "&uploadId=" + uploadID;
        //System.out.println("查询字符串：\n"+url);

        MinioAuthorization auth = new MinioAuthorization();
        auth.setConfig(cfg)
        .setMethod("PUT")
        .setData(data)
        .setUrl(url)
        //.delHead("x-amz-content-sha256")
        .delHead("content-md5")
        .delHead("content-type")
        .setContentType("application/octet-stream; charset=utf-8");

        var authstr = auth.Authorization();
        var c = new HttpClient();

        c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authstr);
        c.DefaultRequestHeaders.Add("x-amz-date", auth.getTimeIso());
        c.DefaultRequestHeaders.Add("x-amz-content-sha256", auth.data_sha256_Hash);
        var mem = new MemoryStream(data);
        var content = new StreamContent(mem,(int)mem.Length);
        var res = await c.PutAsync(url,content);
        if(res.IsSuccessStatusCode&&
            res.Headers.Contains("ETag"))
        {
            var vs = res.Headers.GetValues("ETag").ToArray();
            if(vs.Count()>0)
            {
                return vs[0].Replace("\"", "");
            }
        }
        var msg = await res.Content.ReadAsStringAsync();
        Console.WriteLine(authstr);
        Console.WriteLine(msg);
        throw new Exception(msg);
    }

    /**
     * 参数：pathSvr,minio_id(UploadId)
     * API:
     * 	https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_CompleteMultipartUpload.html
     * POST /Key+?uploadId=UploadId HTTP/1.1
        Host: Bucket.s3.amazonaws.com
        <?xml version="1.0" encoding="UTF-8"?>
        <CompleteMultipartUpload xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
           <Part>
              <ChecksumCRC32>string</ChecksumCRC32>
              <ChecksumCRC32C>string</ChecksumCRC32C>
              <ChecksumSHA1>string</ChecksumSHA1>
              <ChecksumSHA256>string</ChecksumSHA256>
              <ETag>string</ETag>
              <PartNumber>integer</PartNumber>
           </Part>
           ...
        </CompleteMultipartUpload>

     * @param key
     * @param id
     */
    public static async Task<bool> CompleteMultipartUploadAsync(FileInf file)
    {
        //System.out.println("\n minio.合并文件：\n key="+file.S3Key()+"\n UploadId="+file.minio_id);
        MinioConfig cfg = new MinioConfig();
        //http://192.168.0.111:9001/test//123.txt
        String url = cfg.endpoint + "/" + cfg.bucket + "/" + file.S3Key() + "?uploadId=" + file.object_id;
        //System.out.println("查询字符串：\n"+url);

        String etags = file.etags();
        byte[] data = Encoding.UTF8.GetBytes(etags);

        MinioAuthorization auth = new MinioAuthorization();
        auth.setConfig(cfg)
        .setMethod("POST")
        .setUrl(url)
        .setData(data)
        .delHead("Content-Md5")
        .delHead("content-type")
        .setContentType("application/octet-stream; charset=utf-8");

        var authstr = auth.Authorization();
        var c = new HttpClient();
        //c.DefaultRequestHeaders.Add("x-amz-date", auth.getTimeIso());
        //c.DefaultRequestHeaders.Add("x-amz-content-sha256", auth.getDataHash());
        //c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authstr);
        //var content = new StreamContent();
        //content.Add(new ByteArrayContent(data, 0, data.Length));

        var req = new HttpRequestMessage(HttpMethod.Post, url);
        req.Headers.Add("x-amz-date", auth.getTimeIso());
        req.Headers.Add("x-amz-content-sha256", auth.getDataHash());
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authstr);
        //req.Content = new StringContent(etags, new MediaTypeHeaderValue("application/octet-stream"));
        var mem = new MemoryStream(data);
        req.Content = new StreamContent(mem, (int)mem.Length);

        try
        {
            /*
             * HTTP/1.1 200
                x-amz-expiration: Expiration
                x-amz-server-side-encryption: ServerSideEncryption
                x-amz-version-id: VersionId
                x-amz-server-side-encryption-aws-kms-key-id: SSEKMSKeyId
                x-amz-server-side-encryption-bucket-key-enabled: BucketKeyEnabled
                x-amz-request-charged: RequestCharged
                <?xml version="1.0" encoding="UTF-8"?>
                <CompleteMultipartUploadResult>
                   <Location>string</Location>
                   <Bucket>string</Bucket>
                   <Key>string</Key>
                   <ETag>string</ETag>
                   <ChecksumCRC32>string</ChecksumCRC32>
                   <ChecksumCRC32C>string</ChecksumCRC32C>
                   <ChecksumSHA1>string</ChecksumSHA1>
                   <ChecksumSHA256>string</ChecksumSHA256>
                </CompleteMultipartUploadResult>
             */
            var res = await c.SendAsync(req);
            var body = await res.Content.ReadAsStringAsync();
            if (!res.IsSuccessStatusCode)
            {
                Console.WriteLine(body);
                throw new Exception(body);
            }
        }
        catch (WebException e)
        {
            Message m = new Message("minio 合并文件块错误")
                .msg(e)
                .msg(url)
                .msg("etags", etags)
                ;
            return false;
        }
        return true;
    }
}